﻿using System;
using System.Runtime.InteropServices;
using System.Threading;
using NewLife.Reflection;

namespace NewLife.Threading
{
    /// <summary>提供对基于自旋的等待的支持。</summary>
    class SpinWait
    {
        const int YIELD_THRESHOLD = 10;
        const int SLEEP_0_EVERY_HOW_MANY_TIMES = 5;
        const int SLEEP_1_EVERY_HOW_MANY_TIMES = 20;

        private Int32 _Count;
        /// <summary>获取已对此实例调用 <see cref="SpinOnce" /> 的次数。</summary>
        public Int32 Count { get { return _Count; } }

        private static readonly Int32 ProcessorCount = Environment.ProcessorCount;

        /// <summary>获取对 <see cref="SpinOnce" /> 的下一次调用是否将产生处理器，同时触发强制上下文切换。</summary>
        /// <returns>对 <see cref="SpinOnce" /> 的下一次调用是否将产生处理器，同时触发强制上下文切换。</returns>
        public bool NextSpinWillYield
        {
            get
            {
                if (this._Count <= YIELD_THRESHOLD) return ProcessorCount == 1;
                return true;
            }
        }
        /// <summary>执行单一自旋。</summary>
        public void SpinOnce()
        {
            if (this.NextSpinWillYield)
            {
                int num = (this._Count >= YIELD_THRESHOLD) ? (this._Count - YIELD_THRESHOLD) : this._Count;
                if (num % SLEEP_1_EVERY_HOW_MANY_TIMES == SLEEP_1_EVERY_HOW_MANY_TIMES - 1)
                    Thread.Sleep(1);
                else if (num % SLEEP_0_EVERY_HOW_MANY_TIMES == SLEEP_0_EVERY_HOW_MANY_TIMES - 1)
                    Thread.Sleep(0);
                else
                    SwitchToThread();
            }
            else
                Thread.SpinWait(((int)(SLEEP_0_EVERY_HOW_MANY_TIMES - 1)) << this._Count);
            this._Count = (this._Count == Int32.MaxValue) ? YIELD_THRESHOLD : (this._Count + 1);
        }

        [DllImport("Kernel32.dll")]
        extern static Int32 SwitchToThread();

        /// <summary>重置自旋计数器。</summary>
        public void Reset() { this._Count = 0; }

        /// <summary>在指定条件得到满足之前自旋。</summary>
        /// <param name="condition">在返回 true 之前重复执行的委托。</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="condition" /> 参数为 null。</exception>
        public static void SpinUntil(Func<bool> condition) { SpinUntil(condition, -1); }

        /// <summary>在指定条件得到满足或指定超时过期之前自旋。</summary>
        /// <returns>如果条件在超时时间内得到满足，则为 true；否则为 false</returns>
        /// <param name="condition">在返回 true 之前重复执行的委托。</param>
        /// <param name="timeout">一个 <see cref="T:System.TimeSpan" />，表示等待的毫秒数；或者一个 TimeSpan，表示 -1 毫秒（无限期等待）。</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="condition" /> 参数为 null。</exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout" /> 是 -1 毫秒之外的负数，表示无限超时或者超时大于 <see cref="F:System.Int32.MaxValue" />。</exception>
        public static bool SpinUntil(Func<bool> condition, TimeSpan timeout)
        {
            long totalMilliseconds = (long)timeout.TotalMilliseconds;
            if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) throw new ArgumentOutOfRangeException("timeout");
            return SpinUntil(condition, (int)timeout.TotalMilliseconds);
        }

        /// <summary>在指定条件得到满足或指定超时过期之前自旋。</summary>
        /// <returns>如果条件在超时时间内得到满足，则为 true；否则为 false</returns>
        /// <param name="condition">在返回 true 之前重复执行的委托。</param>
        /// <param name="millisecondsTimeout">等待的毫秒数，或为 <see cref="F:System.Threading.Timeout.Infinite" /> (-1)，表示无限期等待。</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="condition" /> 参数为 null。</exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout" /> 是一个非 -1 的负数，而 -1 表示无限期超时。</exception>
        public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
        {
            if (millisecondsTimeout < -1) throw new ArgumentOutOfRangeException("millisecondsTimeout");
            if (condition == null) throw new ArgumentNullException("condition");
            long ticks = 0;
            if (millisecondsTimeout != 0 && millisecondsTimeout != -1) ticks = DateTime.UtcNow.Ticks;
            SpinWait wait = new SpinWait();
            while (!condition())
            {
                if (millisecondsTimeout == 0) return false;
                wait.SpinOnce();
                if (millisecondsTimeout != -1 && wait.NextSpinWillYield && millisecondsTimeout <= (DateTime.UtcNow.Ticks - ticks) / 10000) return false;
            }
            return true;
        }
    }
}